package com.ideaaedi.commonds.clazz;

import com.google.common.collect.Sets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.CharArrayReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 全类名抓取器
 * <p>
 *     全类名形如：<code>com.ideaaedi.commonds.clazz.ClassNameGrabber</code>
 * </p>
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 1.0.0
 */
public class ClassNameGrabber {
    
    /** class类名，必须匹配此正则 */
    public static final Pattern CLASS_NAME_PATTERN =
            Pattern.compile("\\s*(public\\s+|protected\\s+|private\\s+)?(static\\s+)?(final\\s+|abstract\\s+)?(static\\s+)?(class|interface|enum|@interface)\\s+");
    
    /** class类名所在行的前缀 */
    public static final Set<String> CLASS_NAME_LINE_PREFIX_SET =
            Sets.newHashSet("public", "protected", "private",  "static", "final", "abstract", "class", "interface", "enums", "@interface");
    
    /** java文件类型 */
    public static final Set<String> JAVA_FILE_TYPE = Sets.newHashSet("class", "interface", "enums", "@interface");
    
    
    /**
     * @see ClassNameGrabber#obtainFromSourceCode(String)
     */
    public static String obtainFromSourceCode(final byte[] sourceCodeBytes) {
        return ClassNameGrabber.obtainFromSourceCode(new String(sourceCodeBytes, StandardCharsets.UTF_8));
    }
    
    /**
     * 获取全类名
     *
     * @param sourceCode
     *             源码
     * @return 全类名
     */
    public static String obtainFromSourceCode(final String sourceCode) {
        List<String> lines = IOUtils.readLines(new CharArrayReader(sourceCode.toCharArray()));
        String packageName = null;
        int packageNameLine = 1;
        for (String line : lines) {
            // 后面可能有注释，先把可能存在的注释去掉
            int idx = line.indexOf("/");
            if (idx >= 0) {
                line = line.substring(0, idx);
            }
            // 包名形如：   package com.ideaaedi.hot.clazz.update;
            line = line.trim().replace("\\r", "").replace("\\n", "");
            if (line.startsWith("package") && line.endsWith(";")) {
                packageName = line.substring("package".length(), line.length() - 1).trim();
                break;
            }
            packageNameLine++;
        }
        if (packageName == null) {
            throw new IllegalStateException("Cannot parse packageName from follow sourceCode: \n" + sourceCode);
        }
        String className = null;
        String line;
        StringBuilder suspectLine = new StringBuilder(4);
        for (int i = packageNameLine; i < lines.size(); i++) {
            line = lines.get(i);
            // 后面可能有注释，先把可能存在的注释去掉
            int idx = line.indexOf("/");
            if (idx >= 0) {
                line = line.substring(0, idx);
            }
            line = line.trim().replace("\\r", "").replace("\\n", "");
            if (StringUtils.isBlank(line)) {
                continue;
            }
            List<String> segment = Arrays.stream(line.split(" ")).map(String::trim).filter(StringUtils::isNotBlank).collect(Collectors.toList());
            String head = segment.get(0);
            // 可疑行，可疑行可能是类名行
            boolean suspect = CLASS_NAME_LINE_PREFIX_SET.contains(head) && segment.stream().anyMatch(JAVA_FILE_TYPE::contains);
            if (suspect) {
                suspectLine.append(line).append("\n");
            }
            if (suspect && CLASS_NAME_PATTERN.matcher(line).find()) {
                boolean preSegmentIsType = false;
                for (String seg : segment) {
                    seg = seg.trim();
                    if (preSegmentIsType) {
                        int haveGenericIndex = seg.indexOf("<");
                        if (haveGenericIndex >= 0) {
                            seg = seg.substring(0, haveGenericIndex);
                        }
                        className = seg;
                        break;
                    }
                    preSegmentIsType = JAVA_FILE_TYPE.contains(seg.trim());
                }
                break;
            }
        }
        if (StringUtils.isBlank(className)) {
            throw new IllegalStateException("Cannot parse className from sourceCde."
                    + (suspectLine.length() == 0 ? "" : "suspectLine:\n" + suspectLine) + "\nsourceCode:\n" + sourceCode);
        }
        return packageName + "." + className;
    }

}
